﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Mario.Animation;
using Mario.Structures;
using Mario.Characters;
namespace Mario
{
    class Player
    {
        //Keyboard States
        KeyboardState keyboardState;
        KeyboardState prevKeyboardState;
        // Animations
        private Animation.Animation idleAnimation;
        private Animation.Animation runAnimation;
        private Animation.Animation jumpAnimation;
        private Animation.Animation celebrateAnimation;
        private Animation.Animation dieAnimation;
        private Animation.Animation blinkAnimation;
        private SpriteEffects flip = SpriteEffects.None;
        private Animation.AnimationPlayer sprite;

        // Sounds
        private SoundEffect killedSound;
        private SoundEffect jumpSound;
        private SoundEffect fallSound;
        private SoundEffect fireShot;
        // Powerup state
        private const float MaxPowerUpTime = 6.0f;
        public Boolean FireballOn { set; get; }
        private float powerUpTime;
        public bool IsPoweredUp
        {
            get { return powerUpTime > 0.0f; }
        }
        private readonly Color[] poweredUpColors = {
                               Color.Red,
                               Color.Blue,
                               Color.Orange,
                               Color.Yellow,
                                               };
        //private SoundEffect powerUpSound;

        public Level Level
        {
            get { return level; }
        }
        Level level;

        public bool IsAlive
        {
            get { return isAlive; }
        }
        bool isAlive;
        public bool IsBaby
        {
            get { return isBaby; }
        }
        bool isBaby;

        // Physics state
        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }
        Vector2 position;

        private float previousBottom;
        private float previousTop;
        private float previousLeft;
        private float previousRight;
        private  const int  maxDestroyableTiles = 1;
        int countDestroyedTiles;
        public Vector2 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }
        Vector2 velocity;
        private Boolean IsShooting { get; set; }
        // Constants for controling horizontal movement
        private const float MoveAcceleration =9000.0f;
        private const float MaxMoveSpeed = 1000.0f;
        private const float GroundDragFactor = 0.58f;
        private const float AirDragFactor = 0.65f;

        // Constants for controlling vertical movement
        private const float MaxJumpTime = 0.20f;
        private const float JumpLaunchVelocity = -3000.0f;
        private const float GravityAcceleration = 2000.0f;
        private const float MaxFallSpeed = 600.0f;
        private const float JumpControlPower = 0.14f;

        // Input configuration
        private const float MoveStickScale = 1.0f;
        private const Buttons JumpButton = Buttons.A;

        public bool IsOnGround
        {
            get { return isOnGround; }
        }
        bool isOnGround;
        private float movement;

        // Jumping state
        private bool isJumping;
        private bool wasJumping;
        private float jumpTime;
        private float killTime;
        private const float MaxShootTime = 0.30f;
        private float shootTime;

        //Fireballs
        const int maxFireBalls = 2;
        //FireBall[] fireballs = new FireBall[maxFireBalls];
        public  FireBall[] fireBalls;
        private Rectangle localBounds;
        public Rectangle BoundingRectangle
        {
            get
            {
                int left = (int)Math.Round(Position.X - sprite.Origin.X) + localBounds.X;
                int top = (int)Math.Round(Position.Y - sprite.Origin.Y) + localBounds.Y;

                return new Rectangle(left, top, localBounds.Width, localBounds.Height);
            }
        }
        public Player(Level level, Vector2 position)
        {
            this.level = level;
            fireBalls = new FireBall[maxFireBalls];
            for (int i = 0; i < fireBalls.Length; i++)
            {
                fireBalls[i] = new FireBall(level);
            }
            //LoadContent();
            ReloadContent();
            Reset(position);
        }
        public void LoadContent()
        {
            String path = "Sprites/Mario/Normal";
            // Load animated textures.
            idleAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Idle"), 1f, true);
            blinkAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Blink"), 0.1f, true);

            runAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Run"), 0.1f, true);
            jumpAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Jump"), 0.1f, false);
            celebrateAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Attack"), 0.01f, false);
            dieAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Die"), 0.1f, false);

            // Calculate bounds within texture size.            
            int width = (int)(idleAnimation.FrameWidth * 0.5);
            int left = (idleAnimation.FrameWidth - width) / 2;
            int height = (int)(idleAnimation.FrameWidth * 0.8);
            int top = idleAnimation.FrameHeight - height;
            localBounds = new Rectangle(left, top, width, height);

            // Load sounds.            
            killedSound = Level.Content.Load<SoundEffect>("Sounds/die");
            jumpSound = Level.Content.Load<SoundEffect>("Sounds/jump");
            fallSound = Level.Content.Load<SoundEffect>("Sounds/die");
            fireShot = Level.Content.Load<SoundEffect>("Sounds/fireball");
            //powerUpSound = Level.Content.Load<SoundEffect>("Sounds/PowerUp");
          

        }
        public void ReloadContent()
        {
            String path;
            if (isBaby)
            {
                path = "Sprites/Mario/Squished/";
                // Load animated textures.
                idleAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/TIdle"), 1f, true);
                blinkAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/TBlink"), 0.1f, true);

                runAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/TRun"), 0.1f, true);
                jumpAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/TJump"), 0.1f, false);
                //celebrateAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Attack"), 0.1f, false);
                dieAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/TDie"), 0.1f, false);

                // Calculate bounds within texture size.            
                int width = (int)(idleAnimation.FrameWidth * 0.5);
                int left = (idleAnimation.FrameWidth - width) / 2;
                int height = (int)(idleAnimation.FrameWidth * 0.6);
                int top = idleAnimation.FrameHeight - height;
                localBounds = new Rectangle(left, top, width, height);


            }
            else if (FireballOn)
            {
               
                path = "Sprites/Mario/Fire/";
                // Load animated textures.

                //idleAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Bound"), 1f, true);
                idleAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Idle"), 1f, true);
                blinkAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Blink"), 0.1f, true);

                runAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Run"), 0.1f, true);
                jumpAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Jump"), 0.1f, false);
                celebrateAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Attack"), 0.01f, false);
                dieAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Die"), 0.1f, false);

                // Calculate bounds within texture size.            
                int width = (int)(idleAnimation.FrameWidth * 0.5);
                int left = (idleAnimation.FrameWidth - width) / 2;
                int height = (int)(idleAnimation.FrameWidth * 0.8);
                int top = idleAnimation.FrameHeight - height;
                localBounds = new Rectangle(left, top, width, height);

            }
            else
            {
                path = "Sprites/Mario/Normal/";
                // Load animated textures.

                //idleAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Bound"), 1f, true);
                idleAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Idle"), 1f, true);
                blinkAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Blink"), 0.1f, true);

                runAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Run"), 0.1f, true);
                jumpAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Jump"), 0.1f, false);
                celebrateAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Attack"), 0.01f, false);
                dieAnimation = new Animation.Animation(Level.Content.Load<Texture2D>(path + "/Die"), 0.1f, false);

                // Calculate bounds within texture size.            
                int width = (int)(idleAnimation.FrameWidth * 0.5);
                int left = (idleAnimation.FrameWidth - width) / 2;
                int height = (int)(idleAnimation.FrameWidth * 0.8);
                int top = idleAnimation.FrameHeight - height;
                localBounds = new Rectangle(left, top, width, height);

            }
            // Load sounds.            
            killedSound = Level.Content.Load<SoundEffect>("Sounds/die");
            jumpSound = Level.Content.Load<SoundEffect>("Sounds/jump");
            fallSound = Level.Content.Load<SoundEffect>("Sounds/die");
            fireShot = Level.Content.Load<SoundEffect>("Sounds/fireball");
            //powerUpSound = Level.Content.Load<SoundEffect>("Sounds/PowerUp");
        }
        public void Reset(Vector2 position)
        {
            Position = position;
            powerUpTime = 0.0f;

            Velocity = Vector2.Zero;
            isAlive = true;
            isBaby = true;
            sprite.PlayAnimation(idleAnimation);

            ReloadContent();
        }
        public void Update(GameTime gameTime)
        {
            keyboardState = Keyboard.GetState();
            GetInput();
            ApplyPhysics(gameTime);
            foreach (FireBall ball in fireBalls)
                ball.Update(gameTime);
            if (IsPoweredUp)
                powerUpTime = Math.Max(0.0f, powerUpTime - (float)gameTime.ElapsedGameTime.TotalSeconds);
            if (IsShooting)
            {
                if (shootTime <= MaxShootTime)
                {
                    sprite.PlayAnimation(celebrateAnimation);
                    shootTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
                else
                {
                    shootTime = 0;
                    IsShooting = false;
                }
            }
            else
            if (IsAlive && IsOnGround)
            {
                if (Math.Abs(Velocity.X) - 0.02f > 0)
                {
                    sprite.PlayAnimation(runAnimation);
                }
                else
                {
                    if (killTime == -1)
                    {
                        sprite.PlayAnimation(idleAnimation);
                    }
                    else
                    {
                        sprite.PlayAnimation(blinkAnimation);
                    }
                }
            }


            // Clear input.
            movement = 0.0f;
            isJumping = false;
            if (killTime > 0)
            {
                killTime -= (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
            else
            {
                killTime = -1;
            }
            prevKeyboardState = keyboardState;
        }
        private void GetInput()
        {
            // Get input state.
            GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
            

            // Get analog horizontal movement.
            movement = gamePadState.ThumbSticks.Left.X * MoveStickScale;

            // Ignore small movements to prevent running in place.
            if (Math.Abs(movement) < 0.5f)
                movement = 0.0f;

            // If any digital horizontal movement input is found, override the analog movement.
            if (gamePadState.IsButtonDown(Buttons.DPadLeft) ||
                keyboardState.IsKeyDown(Keys.Left) ||
                keyboardState.IsKeyDown(Keys.A))
            {
                movement = -1.0f;
            }
            else if (gamePadState.IsButtonDown(Buttons.DPadRight) ||
                     keyboardState.IsKeyDown(Keys.Right) ||
                     keyboardState.IsKeyDown(Keys.D))
            {
                movement = 1.0f;
            }
            if (keyboardState.IsKeyDown(Keys.T) && prevKeyboardState.IsKeyUp(Keys.T))
            {
                if (isBaby)
                    isBaby = false;
                else
                    isBaby = true;
                ReloadContent();
            }
            if (keyboardState.IsKeyDown(Keys.RightAlt) && prevKeyboardState.IsKeyUp(Keys.RightAlt) && !IsBaby && FireballOn)
            {
                ShootFireBall();
            }
            // Check if the player wants to jump.
            isJumping =
                gamePadState.IsButtonDown(JumpButton) ||
                keyboardState.IsKeyDown(Keys.Space) ||
                keyboardState.IsKeyDown(Keys.Up) ||
                keyboardState.IsKeyDown(Keys.W);
            
        }
        public void GrowUp()
        {
            isBaby = false;
            ReloadContent();
        }
        public void ShootFireBall()
        {
            if(!this.isBaby && FireballOn)
            foreach(FireBall ball in fireBalls)
            {
                if (!ball.Visible)
                {

                    fireShot.Play();
                    IsShooting = true;
                    ball.Visible = true;
                    ball.Position = this.position + new Vector2(0, -10);//-new Vector2(-localBounds.Width/2);
                    ball.Speed=new Vector2(3,0.1f);
                    if (flip==SpriteEffects.None)
                        ball.Direction = 1;
                    else if (flip == SpriteEffects.FlipHorizontally)
                        ball.Direction = -1;
                    
                    return;
                }
            }
        }
        public void ApplyPhysics(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            Vector2 previousPosition = Position;

            // Base velocity is a combination of horizontal movement control and
            // acceleration downward due to gravity.
            velocity.X += movement * MoveAcceleration * elapsed;
            velocity.Y = MathHelper.Clamp(velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);

            velocity.Y = DoJump(velocity.Y, gameTime);

            // Apply pseudo-drag horizontally.
            if (IsOnGround)
                velocity.X *= GroundDragFactor;
            else
                velocity.X *= AirDragFactor;

            // Prevent the player from running faster than his top speed.            
            velocity.X = MathHelper.Clamp(velocity.X, -MaxMoveSpeed, MaxMoveSpeed);

            // Apply velocity.
            Position += velocity * elapsed;
            Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y));

            // If the player is now colliding with the level, separate them.
            HandleCollisions();

            // If the collision stopped us from moving, reset the velocity to zero.
            if (Position.X == previousPosition.X)
                velocity.X = 0;

            if (Position.Y == previousPosition.Y)
                velocity.Y = 0;
        }
        private float DoJump(float velocityY, GameTime gameTime)
        {

            // If the player wants to jump
            if (isJumping)
            {
                
                // Begin or continue a jump
                if ((!wasJumping && IsOnGround) || jumpTime > 0.0f)
                {

                    if (jumpTime == 0.0f)
                    {
                        countDestroyedTiles = 0;
                        jumpSound.Play();
                    }

                    jumpTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
                    sprite.PlayAnimation(jumpAnimation);
                }

                // If we are in the ascent of the jump
                if (0.0f < jumpTime && jumpTime <= MaxJumpTime)
                {
                    // Fully override the vertical velocity with a power curve that gives players more control over the top of the jump
                    velocityY = JumpLaunchVelocity * (1.0f - (float)Math.Pow(jumpTime / MaxJumpTime, JumpControlPower));
                }
                else
                {
                    // Reached the apex of the jump
                    jumpTime = 0.0f;
                }
            }
            else
            {
                // Continues not jumping or cancels a jump in progress
                jumpTime = 0.0f;
            }
            wasJumping = isJumping;

            return velocityY;
        }
        private void HandleCollisions()
        {

            // Get the player's bounding rectangle and find neighboring tiles.
            Rectangle bounds = BoundingRectangle;
            int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
            int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
            int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
            int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
            
            isOnGround = false;

            // For each potentially colliding tile,
            for (int y = topTile; y <= bottomTile; y++)
            {
                for (int x = leftTile; x <= rightTile; x++)
                {
                    // If this tile is collidable,
                    TileCollision collision = Level.GetCollision(x, y);
                    if (collision != TileCollision.Passable && collision !=TileCollision.Invisible)
                    {
                        // Determine collision depth (with direction) and magnitude.
                        Rectangle tileBounds = Level.GetBounds(x, y);
                        Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
                        if (depth != Vector2.Zero)
                        {
                            float absDepthX = Math.Abs(depth.X);
                            float absDepthY = Math.Abs(depth.Y);

                            // Resolve the collision along the shallow axis.
                            if (absDepthY < absDepthX+0.1 || collision == TileCollision.Platform)
                            {
                                // If we crossed the top of a tile, we are on the ground.
                                if (previousBottom <= tileBounds.Top)
                                    isOnGround = true;

                                // Ignore platforms, unless we are on the ground.
                                if (collision == TileCollision.Impassable || collision == TileCollision.Special || collision == TileCollision.Breakable || IsOnGround)
                                {
                                    // Resolve the collision along the Y axis.
                                    Position = new Vector2(Position.X, Position.Y + depth.Y);

                                    // Perform further collisions with the new bounds.
                                    bounds = BoundingRectangle;
                                }
                                if (collision == TileCollision.Breakable)
                                {
                                    Edges currentEdges = Level.GetEdgeCollision(x, y);
                                    if (!isBaby)
                                    {
                                        if (currentEdges.bottomEdge == EdgeCollision.Passable && bounds.Top >= tileBounds.Bottom && countDestroyedTiles < maxDestroyableTiles)
                                        {
                                            Level.OnTileHit(x, y);
                                            countDestroyedTiles++;
                                        }
                                    }
                                }
                                if (collision == TileCollision.Special)
                                {
                                    Edges currentEdges = Level.GetEdgeCollision(x, y);
                                        if (currentEdges.bottomEdge == EdgeCollision.Passable && bounds.Top >= tileBounds.Bottom && countDestroyedTiles < maxDestroyableTiles)
                                        {
                                            Level.OnTileHit(x, y);
                                            countDestroyedTiles++;
                                        }
                                }
                            }
                            else if (collision == TileCollision.Impassable || collision == TileCollision.Breakable || collision == TileCollision.Special) // Ignore platforms.
                            {
                                // Resolve the collision along the X axis.
                                Position = new Vector2(Position.X + depth.X, Position.Y);

                                // Perform further collisions with the new bounds.
                                bounds = BoundingRectangle;
                            }
                        }
                    }
                }
            }

            // Save the new bounds bottom.
            previousTop = bounds.Top;
            previousBottom = bounds.Bottom;
            previousLeft = bounds.Left;
            previousRight = bounds.Right;
        }
        public void OnKilled(Enemy killedBy)
        {
            if (isBaby)
            {
                if (killTime < 0)
                {
                    isAlive = false;
                    if (killedBy != null)
                        killedSound.Play();
                    else
                        fallSound.Play();
                    sprite.PlayAnimation(dieAnimation);
                }
            }
            else
            {
                isBaby = true;
                killTime = 1.0f;
                //position.Y -= 100;

            }
            ReloadContent();
        }
        public void OnFlowerPowerUp()
        {
            if (!IsBaby)
            {
                FireballOn = true;
                ReloadContent();
            }
        }
        public void OnReachedExit()
        {
            sprite.PlayAnimation(celebrateAnimation);
        }
        public void PowerUp()
        {
            powerUpTime = MaxPowerUpTime;
            //powerUpSound.Play();
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            // Flip the sprite to face the way we are moving.
            if (Velocity.X > 0)
                flip = SpriteEffects.None;
            else if (Velocity.X < 0)
                flip = SpriteEffects.FlipHorizontally;
            // Calculate a tint color based on power up state.
            Color color;
            if (IsPoweredUp)
            {
                float t = ((float)gameTime.TotalGameTime.TotalSeconds + powerUpTime / MaxPowerUpTime) * 20.0f;
                int colorIndex = (int)t % poweredUpColors.Length;
                color = poweredUpColors[colorIndex];
            }
            else
            {
                color = Color.White;
            }
            foreach (FireBall ball in fireBalls)
            {
                if (ball.Visible)
                ball.Draw(gameTime, spriteBatch);
            }
            // Draw that sprite.
            sprite.Draw(gameTime, spriteBatch, Position, flip,color);
        }
    }
}
